package com.javayh.redis.conf;

import com.javayh.redis.serializer.RedisObjectSerializer;
import com.javayh.redis.util.RedisUtil;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties.Sentinel;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.Resource;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.util.ReflectionUtils;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * <p>
 * redis自动装配
 * </p>
 *
 * @version 1.0.0
 * @author Dylan-haiji
 * @since 2020/3/4
 */
@Configuration
@AutoConfigureBefore(RedisTemplate.class)
@EnableConfigurationProperties(RedissonProperties.class)
public class RedisAutoConfig {

	@Autowired(required = false)
	private RedissonProperties redissonProperties;

	@Autowired
	private RedisProperties redisProperties;

	@Autowired
	private ApplicationContext ctx;

	@Autowired(required = false)
	private LettuceConnectionFactory lettuceConnectionFactory;

	/**
	 * 适配redis cluster单节点
	 * @return
	 */
	@Primary
	@Bean("redisTemplate")
	// 没有此属性就不会装配bean 如果是单个redis 将此注解注释掉
	// @ConditionalOnProperty(name = "spring.redis.cluster.nodes", matchIfMissing = false)
	public RedisTemplate<String, Object> getRedisTemplate() {
		RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
		redisTemplate.setConnectionFactory(lettuceConnectionFactory);
		RedisSerializer stringSerializer = new StringRedisSerializer();
		RedisSerializer redisObjectSerializer = new RedisObjectSerializer();
		redisTemplate.setKeySerializer(stringSerializer);
		redisTemplate.setHashKeySerializer(stringSerializer);
		redisTemplate.setValueSerializer(redisObjectSerializer);
		redisTemplate.setHashValueSerializer(redisObjectSerializer);
		redisTemplate.afterPropertiesSet();
		redisTemplate.opsForValue().set("hello", "wolrd");
		return redisTemplate;
	}

	/**
	 * 适配redis单节点
	 * @return
	 */
	@Primary
	@Bean("redisTemplate")
	@ConditionalOnProperty(name = "spring.redis.host", matchIfMissing = true)
	public RedisTemplate<String, Object> getSingleRedisTemplate() {
		RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
		RedisSerializer redisObjectSerializer = new RedisObjectSerializer();
		redisTemplate.setConnectionFactory(lettuceConnectionFactory);
		redisTemplate.setKeySerializer(new StringRedisSerializer());
		redisTemplate.setValueSerializer(redisObjectSerializer);
		redisTemplate.setHashValueSerializer(redisObjectSerializer);
		redisTemplate.afterPropertiesSet();
		return redisTemplate;
	}

	@Bean
	public HashOperations<String, String, String> hashOperations(
			StringRedisTemplate stringRedisTemplate) {
		return stringRedisTemplate.opsForHash();
	}

	/**
	 * redis工具类
	 */
	@Bean("redisUtil")
	public RedisUtil redisUtil(RedisTemplate<String, Object> redisTemplate,
			StringRedisTemplate stringRedisTemplate,
			HashOperations<String, String, String> hashOperations) {
		RedisUtil redisUtil = new RedisUtil(redisTemplate, stringRedisTemplate,
				hashOperations);
		return redisUtil;
	}

	@Bean(destroyMethod = "shutdown")
	@ConditionalOnMissingBean(RedissonClient.class)
	public RedissonClient redissonClient() throws IOException {
		Config config = null;
		Method clusterMethod = ReflectionUtils.findMethod(RedisProperties.class,
				"getCluster");
		Method timeoutMethod = ReflectionUtils.findMethod(RedisProperties.class,
				"getTimeout");
		Object timeoutValue = ReflectionUtils.invokeMethod(timeoutMethod,
				redisProperties);
		int timeout;
		if (null == timeoutValue) {
			timeout = 60000;
		}
		else if (!(timeoutValue instanceof Integer)) {
			Method millisMethod = ReflectionUtils.findMethod(timeoutValue.getClass(),
					"toMillis");
			timeout = ((Long) ReflectionUtils.invokeMethod(millisMethod, timeoutValue))
					.intValue();
		}
		else {
			timeout = (Integer) timeoutValue;
		}
		// spring.redis.redisson.config=classpath:redisson.yaml
		if (redissonProperties.getConfig() != null) {

			try {
				InputStream is = getConfigStream();
				config = Config.fromJSON(is);
			}
			catch (IOException e) {
				// trying next format
				try {
					InputStream is = getConfigStream();
					config = Config.fromYAML(is);
				}
				catch (IOException e1) {
					throw new IllegalArgumentException("Can't parse config", e1);
				}
			}
		}
		else if (redisProperties.getSentinel() != null) {
			// 哨兵配置
			Method nodesMethod = ReflectionUtils.findMethod(Sentinel.class, "getNodes");
			Object nodesValue = ReflectionUtils.invokeMethod(nodesMethod,
					redisProperties.getSentinel());

			String[] nodes;
			if (nodesValue instanceof String) {
				nodes = convert(Arrays.asList(((String) nodesValue).split(",")));
			}
			else {
				nodes = convert((List<String>) nodesValue);
			}

			config = new Config();
			config.useSentinelServers()
					.setMasterName(redisProperties.getSentinel().getMaster())
					.addSentinelAddress(nodes).setDatabase(redisProperties.getDatabase())
					.setConnectTimeout(timeout)
					.setPassword(redisProperties.getPassword());
		}
		else if (clusterMethod != null
				&& ReflectionUtils.invokeMethod(clusterMethod, redisProperties) != null) {
			// 集群配置
			Object clusterObject = ReflectionUtils.invokeMethod(clusterMethod,
					redisProperties);
			Method nodesMethod = ReflectionUtils.findMethod(clusterObject.getClass(),
					"getNodes");
			List<String> nodesObject = (List) ReflectionUtils.invokeMethod(nodesMethod,
					clusterObject);

			String[] nodes = convert(nodesObject);

			config = new Config();
			config.useClusterServers().addNodeAddress(nodes).setConnectTimeout(timeout)
					.setPassword(redisProperties.getPassword());
		}
		else {
			// 单机redssion默认配置
			config = new Config();
			String prefix = "redis://";
			Method method = ReflectionUtils.findMethod(RedisProperties.class, "isSsl");
			if (method != null
					&& (Boolean) ReflectionUtils.invokeMethod(method, redisProperties)) {
				prefix = "rediss://";
			}

			config.useSingleServer()
					.setAddress(prefix + redisProperties.getHost() + ":"
							+ redisProperties.getPort())
					.setConnectTimeout(timeout).setDatabase(redisProperties.getDatabase())
					.setPassword(redisProperties.getPassword());

		}

		return Redisson.create(config);
	}

	private String[] convert(List<String> nodesObject) {
		List<String> nodes = new ArrayList<String>(nodesObject.size());
		for (String node : nodesObject) {
			if (!node.startsWith("redis://") && !node.startsWith("rediss://")) {
				nodes.add("redis://" + node);
			}
			else {
				nodes.add(node);
			}
		}
		return nodes.toArray(new String[nodes.size()]);
	}

	private InputStream getConfigStream() throws IOException {
		Resource resource = ctx.getResource(redissonProperties.getConfig());
		InputStream is = resource.getInputStream();
		return is;
	}

}
